package adapter

import (
	"bytes"
	"database/sql"
	"errors"
	"fmt"
	"runtime"
	"strings"

	"github.com/casbin/casbin/v2/model"
	"github.com/casbin/casbin/v2/persist"
)

var (
	// ErrNoImpl not inplemented -- 未实现
	ErrNoImpl = errors.New("not implemented")
)

type ErrPolicy struct {
	Type string
	Errs []error
}

func (err ErrPolicy) Error() string {
	b := bytes.NewBuffer([]byte{})

	b.WriteString(fmt.Sprintf("[%s]:", err.Type))
	for _, e := range err.Errs {
		b.WriteString(fmt.Sprintf("\n%v", e))
	}
	return b.String()
}

type CasbinRule struct {
	PType string
	V0    string
	V1    string
	V2    string
}

func (cr CasbinRule) LoadLine() (res string) {
	if cr.PType != "" {
		res = cr.PType
	}
	if cr.V0 != "" {
		res = res + ", " + cr.V0
	}
	if cr.V1 != "" {
		res = res + ", " + cr.V1
	}
	if cr.V2 != "" {
		res = res + ", " + cr.V2
	}
	return
}

// Adapter 为 route/mysql-mode 建立的 casbin/Adapter
type Adapter struct {
	db   *sql.DB
	stmt map[string]map[string][]*sql.Stmt
	// mutex sync.Mutex
}

func (a *Adapter) GetStmt(key1, key2 string, index int) *sql.Stmt {
	// 初始化在创建时, 那么在什么情况下需要锁?
	// a.mutex.Lock()
	// defer a.mutex.Unlock()
	return a.stmt[key1][key2][index]
}

// NewAdapter 新建 Adapter
// 	因为 mysql-mode 是一个单件, 不方便重新生成一个 \\- v -\\
//
// TODO: 重新实现 mysql-mode 以便可以有 createdatabase 方法
func NewAdapter(db *sql.DB) (a *Adapter, err error) {
	if db == nil {
		return nil, errors.New("sql.DB MUST be set")
	}

	stmt, err := initPermission(prepare, db)
	if err != nil {
		return nil, err
	}

	a = &Adapter{
		db:   db,
		stmt: stmt,
	}

	runtime.SetFinalizer(a, finalizer) // 析构函数

	return a, nil
}

// TODO:
func (a *Adapter) createdatabase() error {
	return errors.New("create database failed")
}

// LoadPolicy load policy from mysql
func (a *Adapter) LoadPolicy(model model.Model) error {
	// relation_admin_user_permission
	{
		// 读 person.num_job
		rows1, err1 := a.GetStmt("select", "user_permission", 0).Query() // a.stmt["select"]["relation_user_permission"][0].Query()
		// 读 admin_permission.name
		rows2, err2 := a.GetStmt("select", "user_permission", 1).Query() // a.stmt["select"]["relation_user_permission"][1].Query()
		// 读 relation_admin_user_permission.level
		rows3, err3 := a.GetStmt("select", "user_permission", 2).Query() // a.stmt["select"]["relation_user_permission"][2].Query()
		if err1 != nil || err2 != nil || err3 != nil {
			return &ErrPolicy{Type: "Load Policy", Errs: []error{err1, err2, err3}}
		}

		for rows1.Next() && rows2.Next() && rows3.Next() {
			cr := CasbinRule{PType: "p"}
			rows1.Scan(&cr.V0)
			rows2.Scan(&cr.V1)
			rows3.Scan(&cr.V2)
			cr.V0 = "user::" + cr.V0

			a.loadPolicyLine(cr, model)
		}
		rows1.Close()
		rows2.Close()
		rows3.Close()
	}

	// relation_admin_role_permission
	{
		// 读 admin_role.name
		rows1, err1 := a.GetStmt("select", "role_permission", 0).Query() // a.stmt["select"]["relation_role_permission"][0].Query()
		// 读 admin_permission.name
		rows2, err2 := a.GetStmt("select", "role_permission", 1).Query() // a.stmt["select"]["relation_role_permission"][1].Query()
		// 读 relation_admin_user_permission.level
		rows3, err3 := a.GetStmt("select", "role_permission", 2).Query() // a.stmt["select"]["relation_role_permission"][2].Query()
		if err1 != nil || err2 != nil || err3 != nil {
			return &ErrPolicy{Type: "Load Policy", Errs: []error{err1, err2, err3}}
		}

		for rows1.Next() || rows2.Next() || rows3.Next() {
			cr := CasbinRule{PType: "p"}
			rows1.Scan(&cr.V0)
			rows2.Scan(&cr.V1)
			rows3.Scan(&cr.V2)
			cr.V0 = "role::" + cr.V0

			a.loadPolicyLine(cr, model)
		}
		rows1.Close()
		rows2.Close()
		rows3.Close()
	}

	// relation_admin_role_user
	{
		// 读 admin_role.name
		rows1, err1 := a.GetStmt("select", "role_user", 0).Query() // a.stmt["select"]["relation_role_user"][0].Query()
		// 读 admin_permission.name
		rows2, err2 := a.GetStmt("select", "role_user", 1).Query() // a.stmt["select"]["relation_role_user"][1].Query()
		// 读 relation_admin_user_permission.level
		if err1 != nil || err2 != nil {
			return &ErrPolicy{Type: "Load Policy", Errs: []error{err1, err2}}
		}

		for rows1.Next() || rows2.Next() {
			cr := CasbinRule{PType: "g"}
			rows1.Scan(&cr.V0)
			rows2.Scan(&cr.V1)
			cr.V0 = "user::" + cr.V0
			cr.V1 = "role::" + cr.V1

			a.loadPolicyLine(cr, model)
		}
		rows1.Close()
		rows2.Close()
	}

	return nil
}

func (a *Adapter) loadPolicyLine(line CasbinRule, model model.Model) {
	persist.LoadPolicyLine(line.LoadLine(), model)
}

// SavePolicy save policy to mysql
func (a *Adapter) SavePolicy(model model.Model) error {
	a.dropTable()
	a.createTable()

	// var personPmLines, rolePmLines, rolePersonLines []CasbinRule
	var ppJobNums, ppPmNames, ppLevels []interface{}
	var rpNames, rpPmNames, rpLevels []interface{}
	var ruRoleNames, ruJobNums []interface{}

	for ptype, ast := range model["p"] {
		for _, rule := range ast.Policy {
			// subject 前缀必须有 user:: 或者 role::
			if len(rule[0]) < 6 {
				continue
			}

			line := policyLine(ptype, rule)
			if policyIsUser(rule[0]) {
				line.V0 = rule[0][6:]
				// personPmLines = append(personPmLines, line)
				ppJobNums = append(ppJobNums, line.V0)
				ppPmNames = append(ppPmNames, line.V1)
				ppLevels = append(ppLevels, line.V2)
			} else {
				line.V0 = rule[0][6:]
				// rolePmLines = append(rolePmLines, line)
				rpNames = append(rpNames, line.V0)
				rpPmNames = append(rpPmNames, line.V1)
				rpLevels = append(rpLevels, line.V2)
			}
		}
	}

	for ptype, ast := range model["g"] {
		for _, rule := range ast.Policy {
			// subject 前缀必须有 user:: 或者 role::
			// 目前只有 用户-角色 的继承关系, 没有 用户-用户/角色-角色 的继承关系
			if len(rule[0]) < 6 || len(rule[1]) < 6 ||
				rule[0][:6] != "user::" || rule[1][:6] != "role::" {
				continue
			}
			line := policyLine(ptype, rule)
			line.V0 = line.V0[6:]
			line.V1 = line.V1[6:]
			// rolePersonLines = append(rolePersonLines, line)
			ruJobNums = append(ruJobNums, line.V0)
			ruRoleNames = append(ruRoleNames, line.V1)
		}
	}

	// 允许没有 继承 关系
	if len(ppPmNames) == 0 && len(rpPmNames) == 0 {
		return nil
	}

	// mysql 插入
	// relation_admin_user_permission
	if err := userPermissionSavePolicy(a.db, ppJobNums, ppPmNames, ppLevels); err != nil {
		return err
	}

	// relation_admin_role_permission
	if err := rolePermissionSavePolicy(a.db, rpNames, rpPmNames, rpLevels); err != nil {
		return err
	}

	// relation_admin_role_user
	if err := roleUserSavePolicy(a.db, ruJobNums, ruRoleNames); err != nil {
		return err
	}

	return nil
}

// relation_admin_user_permission
func userPermissionSavePolicy(db *sql.DB, jobNums, pmNames, levels []interface{}) error {
	queryUser := "SELECT u.id FROM admin_user AS u INNER JOIN (" +
		selectQueryIn("person.id", "person", len(jobNums), "person.num_job") +
		" ) AS p ON p.id = u.id_person"
	queryPm := selectQueryIn("id", "admin_permisson", len(pmNames), "name")

	rowsUser, err1 := db.Query(queryUser, jobNums...)
	rowsPm, err2 := db.Query(queryPm, pmNames...)
	if err1 != nil || err2 != nil {
		return ErrPolicy{Type: "user|permission", Errs: []error{err1, err2}}
	}
	var insertQuery []interface{}
	var userID, pmID int
	i := 0
	for rowsUser.Next() && rowsPm.Next() {
		rowsUser.Scan(&userID)
		rowsPm.Scan(&pmID)
		insertQuery = append(insertQuery, userID, pmID, levels[i])
		i++
	}
	rowsUser.Close()
	rowsPm.Close()

	db.Exec(insertIntoRelation("relation_admin_user_permission",
		[]string{"id_admin_user", "id_admin_permission", "level"}, len(insertQuery)/3),
		insertQuery...)
	return nil
}

// relation_admin_role_permission
func rolePermissionSavePolicy(db *sql.DB, roleNames, pmNames, levels []interface{}) error {
	queryRole := selectQueryIn("id", "admin_role", len(roleNames), "name")
	queryPm := selectQueryIn("id", "admin_permisson", len(pmNames), "name")

	rowsRole, err1 := db.Query(queryRole, roleNames...)
	rowsPm, err2 := db.Query(queryPm, pmNames...)
	if err1 != nil || err2 != nil {
		return ErrPolicy{Type: "role|permission", Errs: []error{err1}}
	}
	var insertQuery []interface{}
	var roleID, pmID int
	i := 0
	for rowsRole.Next() && rowsPm.Next() {
		rowsRole.Scan(&roleID)
		rowsPm.Scan(&pmID)
		insertQuery = append(insertQuery, roleID, pmID, levels[i])
		i++
	}
	rowsRole.Close()
	rowsPm.Close()

	db.Exec(insertIntoRelation("relation_admin_role_permission",
		[]string{"id_admin_role", "id_admin_permission", "level"}, len(insertQuery)/3),
		insertQuery...)
	return nil
}

// relation_admin_role_user
func roleUserSavePolicy(db *sql.DB, roleNames, jobNums []interface{}) error {
	queryRole := selectQueryIn("id", "admin_role", len(roleNames), "name")
	queryUser := "SELECT u.id FROM admin_user AS u INNER JOIN (" +
		selectQueryIn("person.id", "person", len(jobNums), "person.num_job") +
		" ) AS p ON p.id = u.id_person"

	rowsRole, err2 := db.Query(queryRole, roleNames...)
	rowsUser, err1 := db.Query(queryUser, jobNums...)
	if err1 != nil || err2 != nil {
		return ErrPolicy{Type: "role|user", Errs: []error{err1, err2}}
	}
	var insertQuery []interface{}
	var roleID, userID int
	i := 0
	for rowsRole.Next() && rowsUser.Next() {
		rowsRole.Scan(&roleID)
		rowsUser.Scan(&userID)
		insertQuery = append(insertQuery, roleID, userID)
		i++
	}
	rowsRole.Close()
	rowsUser.Close()

	db.Exec(insertIntoRelation("relation_admin_role_user",
		[]string{"id_admin_role", "id_admin_user"}, len(insertQuery)/2),
		insertQuery...)
	return nil
}

func selectQueryIn(columns string, tb1 string, whereCount int, where string) (query string) {
	buf := bytes.NewBuffer(make([]byte, 0, 4096))
	buf.WriteString(fmt.Sprintf("SELECT %s FROM %s WHERE %s IN ", columns, tb1, where))
	wheres := "("
	for i := 0; i < whereCount; i++ {
		wheres += "?, "
	}

	wheres = wheres[:len(wheres)-2] + ")"
	buf.WriteString(wheres)
	return buf.String()
}

func insertIntoRelation(tb string, columns []string, valueCount int) (insert string) {
	buf := bytes.NewBuffer(make([]byte, 0, 4096))
	count := len(columns)
	value := "("
	for i := 0; i < count; i++ {
		value += "?, "
	}
	value = value[:len(value)-2]
	value += "), "
	values := ""
	for i := 0; i < valueCount; i++ {
		values += value
	}
	values = values[:len(values)-2]
	buf.WriteString("INSERT INTO " + tb + "(" + strings.Join(columns, ", ") + ") VALUES " + values)
	return buf.String()
}

func policyLine(ptype string, rule []string) CasbinRule {
	line := CasbinRule{}
	line.PType = ptype

	if len(rule) > 0 {
		line.V0 = rule[0]
	}
	if len(rule) > 1 {
		line.V1 = rule[1]
	}
	if len(rule) > 2 {
		line.V2 = rule[2]
	}

	return line
}

func policyIsUser(subject string) bool {
	if subject[:6] == "user::" {
		return true
	}
	return false
}

func (a *Adapter) close() {
	a.db = nil
}

// 删除关联表:
// relation_admin_role_user relation_admin_role_permission relation_admin_user_permission
func (a *Adapter) dropTable() {
	// a.stmt["drop"]["user_permission"][0].Exec()
	// a.stmt["drop"]["role_permission"][0].Exec()
	// a.stmt["drop"]["role_user"][0].Exec()
	a.GetStmt("drop", "user_permission", 0).Exec()
	a.GetStmt("drop", "role_permission", 0).Exec()
	a.GetStmt("drop", "role_user", 0).Exec()
}

// 创建关联表:
func (a *Adapter) createTable() {
	// a.stmt["create"]["user_permission"][0].Exec()
	// a.stmt["create"]["role_permission"][0].Exec()
	// a.stmt["create"]["role_user"][0].Exec()
	a.GetStmt("create", "user_permission", 0).Exec()
	a.GetStmt("create", "role_permission", 0).Exec()
	a.GetStmt("create", "role_user", 0).Exec()
}

// AddPolicy add policy to mysql
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
	cr := policyLine(ptype, rule)

	// p 的模型匹配在 enforce 层已经过滤了一遍, 可以直接写
	if ptype == "p" && rule[0][:6] == "user::" {

		cr.V0 = cr.V0[6:]
		_, err := a.GetStmt("insert", "user_permission", 0).Exec(cr.V0, cr.V1, cr.V2) // a.stmt["insert"]["user_permission"][0].Exec(cr.V0, cr.V1)
		if err != nil {
			return ErrPolicy{Type: "Add Policy", Errs: []error{err}}
		}
		// TODO: 把以下两行也压到 insert into ... select 中
		// i, err := res.LastInsertId()
		// a.stmt["update"]["user_permission"][0].Exec(cr.V2, i)
		// a.GetStmt("update", "user_permission", 0).Exec(cr.V2, i)

	} else if ptype == "p" && rule[0][:6] == "role::" {

		cr.V0 = cr.V0[6:]
		_, err := a.GetStmt("insert", "role_permission", 0).Exec(cr.V0, cr.V1, cr.V2) // a.stmt["insert"]["role_permission"][0].Exec(cr.V0, cr.V1)
		if err != nil {
			return ErrPolicy{Type: "Add Policy", Errs: []error{err}}
		}
		// TODO: 把以下两行也压到 insert into ... select 中
		// i, err := res.LastInsertId()
		// a.stmt["update"]["role_permission"][0].Exec(cr.V2, i)
		// a.GetStmt("update", "role_permission", 0).Exec(cr.V2, i)

	} else if ptype == "g" && rule[0][:6] == "user::" && rule[1][:6] == "role::" {

		cr.V0, cr.V1 = cr.V0[6:], cr.V1[6:]
		_, err := a.GetStmt("insert", "role_user", 0).Exec(cr.V0, cr.V1) // a.stmt["insert"]["role_user"][0].Exec(cr.V0, cr.V1)
		if err != nil {
			return ErrPolicy{Type: "Add Policy", Errs: []error{err}}
		}

	} else {
		return errors.New("unknown error")
	}

	return nil
}

// RemovePolicy delete policy from mysql
// TODO:
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
	cr := policyLine(ptype, rule)

	// p 的模型匹配在 enforce 层已经过滤了一遍, 可以直接写
	if ptype == "p" && rule[0][:6] == "user::" {

		cr.V0 = cr.V0[6:]
		_, err := a.GetStmt("delete", "user_permission", 0).Exec(cr.V0, cr.V1, cr.V2) // a.stmt["delete"]["user_permission"][0].Exec(cr.V0, cr.V1, cr.V2)
		if err != nil {
			return ErrPolicy{Type: "Remove Policy", Errs: []error{err}}
		}

	} else if ptype == "p" && rule[0][:6] == "role::" {

		cr.V0 = cr.V0[6:]
		_, err := a.GetStmt("delete", "role_permission", 0).Exec(cr.V0, cr.V1, cr.V2) // a.stmt["delete"]["role_permission"][0].Exec(cr.V0, cr.V1, cr.V2)
		if err != nil {
			return ErrPolicy{Type: "Remove Policy", Errs: []error{err}}
		}

	} else if ptype == "g" && rule[0][:6] == "user::" && rule[1][:6] == "role::" {

		cr.V0, cr.V1 = cr.V0[6:], cr.V1[6:]
		_, err := a.GetStmt("select", "role_user", 0).Exec(cr.V0, cr.V1) // a.stmt["delete"]["role_user"][0].Exec(cr.V0, cr.V1)
		if err != nil {
			return ErrPolicy{Type: "Remove Policy", Errs: []error{err}}
		}

	} else {
		return errors.New("unknown error")
	}

	return nil
}

// RemoveFilteredPolicy ??
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
	return ErrNoImpl
}

// finalizer 析构函数
// 析构需要关闭 stmt
func finalizer(a *Adapter) {
	a.db = nil
	for k, v := range a.stmt {
		for _, sv := range v {
			for _, ssv := range sv {
				ssv.Close()
			}
		}
		delete(a.stmt, k)
	}
}
